import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.Timer;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;

/**
 * 游戏主面板
 * @author traveller
 * @create 2020-07-20 9:17
 * @since 1.0.0
 */
public class GameFrame extends JFrame implements ActionListener,MouseListener, MouseMotionListener {

    //地图数据
    private byte[][] map;
    //分割图片数组
    private Image[][] images;
    //map列表
    private byte[][][] totalMap;
    //记录map中方块的集合，用于随机取
    private HashMap<String,Integer> blockMap = new HashMap<>();
    //背景图片
    private final BackgroundPanel backgroundPanel = new BackgroundPanel();
    //当方块图片被点击时，在map中的坐标，根据此坐标绘制选中图标样式 select.png
    private int select_x = -1;
    private int select_y = -1;
    //图片缓存
    private Image offScreenImage;
    //闪电动画数组 横线
    private Image[] lightningXImages;
    //闪电动画数组 竖线
    private Image[] lightningYImages;
    //爆炸动画数组
    private Image[] explodeImages;
    //闪电动画帧数
    private int currentLightningIndex = -1;
    //爆炸动画帧数
    private int currentExplodeIndex = -1;
    //动画定时器
    private Timer animationTimmer;
    //闪电绘制类型 可能情况有 一条横，一条竖，二条横竖，二条竖横，三条横竖横，三条竖横竖
    private enum LightningTypes {X,Y,XY,YX,XYX,YXY};
    private LightningTypes lightning_type;
    //闪电绘制路径坐标点，最多有3条线
    private Point point_begin_fst;
    private Point point_end_fst;
    private Point point_begin_sec;
    private Point point_end_sec;
    private Point point_begin_thr;
    private Point point_end_thr;
    //初始化游戏时必须将所有Image绘制一次，才能保证屏幕不闪烁
    private boolean initGraphics = true;
    //方块底部图片
    private Image blockImage;
    //方块灰色图片，用于地图编辑
    private Image blockGrayImage;
    //选择方块图片
    private Image selectImage;
    //声音对象
    private Sound sound;
    //游戏进度 -1加载音效资源 0准备就绪 1游戏模式 2地图编缉模式
    private int gameProgress = -1;
    //地图标签
    private JLabel comboboxLabel;
    //地图下拉框
    private JComboBox mapComboBox = new JComboBox();
    //字体省略号动态计数
    private int ellipsisCount = 0;
    //按钮
    private JButton startButton;
    private JButton editButton;
    private JButton saveButton;
    private JButton cancelButton;
    private JButton rearrangementButton;
    //默认连连看区域起始的坐标
    private final int area_x = 13;
    private final int area_y = 214;
    private final int area_end_x = 602;
    //绘制方块的坐标包括了标题框的范围(y轴占30像素)，但是监听鼠标是监听的背景图片，不包括标题框
    //所以监听鼠标判断范围要 y轴少30像素
    private final int area_begin_y = 214 - 30;
    private final int area_end_y = 599 - 30;
    //全局方块总数，用于paint刷新
    private int countBlock;
    //map目录路径
    public String mapPath;

    /**
     * 游戏主面板初始化
     */
    public GameFrame(){

        super("QQ连连看JAVA高仿版V1.0 by 旅者GameMaker 2020-07-20");//标题

        setSize(810,638);//窗口大小
        setLocationRelativeTo(null);//窗口自动屏幕居中
        setResizable(false);//禁止玩家改变窗口大小
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//点击叉退出游戏
        setLayout(null);
        Container cont = getContentPane();

        //设置背景图
        backgroundPanel.setImage(loadImage("image/bg.bmp"));
        backgroundPanel.setBounds(1, 1, 800, 600);
        cont.add(backgroundPanel);

        //将方块图标分隔成小图，放入数组中
        imageCut();

        //获取map目录路径
        String path  = getClass().getResource("map").toString();
        mapPath = path.substring(6,path.length());
        //如果要导出jar，就解除下面这行的注释
        // mapPath = exportJarMapDir(mapPath);

        //加载所有地图文件，并刷新下拉框数值
        loadMap();

        //设置下拉框
        mapComboBox.setBounds(685,513,100,30);
        backgroundPanel.add(mapComboBox);
        //设置下拉框解释标签
        comboboxLabel = new JLabel("地图选择:");
        comboboxLabel.setBounds(620,513,100,30);
        backgroundPanel.add(comboboxLabel);

        //添加开始按钮
        startButton = new JButton(new ImageIcon(loadImage("image/start.png")));
        startButton.setBounds(620,550,78,36);
        backgroundPanel.add(startButton);

        //添加编辑按钮
        editButton = new JButton(new ImageIcon(loadImage("image/edit.png")));
        editButton.setBounds(705,550,79,36);
        backgroundPanel.add(editButton);

        //添加保存按钮
        saveButton = new JButton(new ImageIcon(loadImage("image/save.png")));
        saveButton.setBounds(620,550,78,36);
        backgroundPanel.add(saveButton);

        //添加取消按钮
        cancelButton = new JButton(new ImageIcon(loadImage("image/cancel.png")));
        cancelButton.setBounds(705,550,79,36);
        backgroundPanel.add(cancelButton);

        //添加重列按钮
        rearrangementButton = new JButton(new ImageIcon(loadImage("image/rearrangement.png")));
        rearrangementButton.setBounds(620,190,24,24);
        backgroundPanel.add(rearrangementButton);

        //加入监听
        startButton.addMouseListener(this);
        editButton.addMouseListener(this);
        saveButton.addMouseListener(this);
        cancelButton.addMouseListener(this);
        rearrangementButton.addMouseListener(this);
        backgroundPanel.addMouseListener(this);
        backgroundPanel.addMouseMotionListener(this);//鼠标移动监听
        mapComboBox.addActionListener(this);

        //按钮、下拉框默认隐藏
        comboboxLabel.setVisible(false);
        mapComboBox.setVisible(false);
        startButton.setVisible(false);
        editButton.setVisible(false);
        saveButton.setVisible(false);
        cancelButton.setVisible(false);
        rearrangementButton.setVisible(false);

        //根据选中地图生成随机方块
        randomBlock(false);

        //加载动画帧
        try {
            loadAnimation();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }

        setVisible(true);
    }

    /**
     * 根据图片名称加载图片，
     * 不用getClass().getResource()是因为导出jar后路径会额外多一个感叹号!导致找不到目录
     */
    private Image loadImage(String imageDir){
        InputStream is = this.getClass().getResourceAsStream(imageDir);
        BufferedImage read = null;
        try {
            read = ImageIO.read(is);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return read;
    }

    /**
     * 加载所有地图文件，并刷新下拉框
     */
    private void loadMap(){
        //清空下拉框
        mapComboBox.removeAllItems();
        //读取地图文件(自定义地图文件.map结尾，每个map文件就是一个地图)
        File file = new File(mapPath);

        //有文件，就读取map数据
        if(file.list().length > 0){
            //如果map目录有文件，统计所有.map结尾的文件
            String[] fileList = file.list();
            int count = 0;
            ArrayList<Object> byteList = new ArrayList<>();
            for (int i = 0; i < fileList.length; i++) {
                if(fileList[i].endsWith(".map")){
                    mapComboBox.addItem(" ● " + fileList[i].substring(0,fileList[i].length() - 4));//添加到下拉框中
                    byte[][] bytes = readMap(fileList[i], mapPath, count);
                    byteList.add(bytes);
                    count++;
                }
            }
            if(byteList.size() != 0){
                byte[][][] temp = new byte[byteList.size()][11][19];
                for (int i = 0; i < byteList.size(); i++) {
                    temp[i] = (byte[][])byteList.get(i);
                }
                totalMap = temp;
            }
        }
        //如果没有地图文件或文件列表中没有map文件，默认生成 hi.map
        if(totalMap == null || totalMap.length == 0){
            totalMap = MapEditer.getDefaultMap(this);
            mapComboBox.addItem(" ● hi");//添加到下拉框中
        }
    }

    /**
     * 加载图片、动画帧
     */
    private void loadAnimation() throws MalformedURLException {
        //闪电动画帧 横线
        Image[] tempImage = new Image[5];
        for (int i = 0; i < 5; i++) {
            tempImage[i] = loadImage("animation/lightning_x/lightning_x_" + (i + 1) + ".png");
        }
        lightningXImages = tempImage;
        //闪电动画帧 竖线
        Image[] tempImage1 = new Image[5];
        for (int i = 0; i < 5; i++) {
            tempImage1[i] = loadImage("animation/lightning_y/lightning_y_" + (i + 1) + ".png");
        }
        lightningYImages = tempImage1;
        //爆炸动画帧
        Image[] tempImage2 = new Image[37];
        for (int i = 0; i < 37; i++) {
            tempImage2[i] = loadImage("animation/explode/explode_" + (i + 1) + ".png");
        }
        explodeImages = tempImage2;
        //方块底部图片
        blockImage = loadImage("image/block.png");
        //方块灰色图片
        blockGrayImage = loadImage("image/block_gray.png");
        //选中方块图片
        selectImage = loadImage("image/select.png");
     }

    /**
     * 绘制方块图
     * @param g
     */
    @Override
    public void paint(Graphics g) {
        super.paint(g);

        //由于双缓冲必须要image至少出现过一次之后才能屏幕不闪烁，所以第一次初始化就全部Image加载一遍，以后就不会屏幕闪烁了
        //如果要对项目进行改动增加新图片，记得这里也要加进去，width，height参数要正确填写，不然还是会闪烁
        if(initGraphics){
            initGraphics = false;
            g.drawImage(blockImage, 0,0, 34, 39,this);
            g.drawImage(selectImage, 0,0, 31, 35,this);
            for (int i = 0; i < 37; i++) {
                if(i < 5){
                    g.drawImage(lightningXImages[i],0,0,35,30,this);
                    g.drawImage(lightningYImages[i],0,0,35,30,this);
                }
                g.drawImage(explodeImages[i],0,0,35,30,this);
            }
        }

        //游戏进度
        //-1 无法进行任何操作，显示加载资源中(音频加载太慢)
        //0  准备就绪，显示游戏帮助，玩家可以选择地图，然后点击开始或编缉按钮
        //1  正在游戏中，玩家可正常进行游戏
        //2  地图编辑模式
        if(gameProgress == -1){
            g.setFont(new Font(null,Font.BOLD,30));
            g.setColor(Color.ORANGE);
            int ellipsis = 25 + ellipsisCount * 25;
            if(ellipsis >= 100){//有时候突然飙升到好几百，不知道原因
                ellipsis = 95;
            }
            g.drawString("音效资源加载中，请稍后(" + ellipsis + "%)",100,570);
            ellipsisCount++;
            return;
        }

        if(gameProgress == 0){
            g.setFont(new Font(null,Font.ROMAN_BASELINE,20));
            g.setColor(Color.YELLOW);
            g.drawString("欢迎来到【QQ连连看JAVA高仿单机版V1.0】",80,280);
            g.drawString("1、右下角下拉框选择地图，点击开始按钮进行游戏",80,340);
            g.drawString("2、点击编辑按钮可以对下拉框选择的地图进行编辑",80,380);
            g.drawString("3、新增地图方法：编辑完地图后保存为新名字即可",80,420);
            g.drawString("by  旅者GameMaker",380,480);
            return;
        }

        //地图编辑模式和正常游戏都需要方块总数
        g.setFont(new Font(null,Font.ROMAN_BASELINE,15));
        g.setColor(Color.YELLOW);
        g.drawString(countBlock + "",548,618);

        //地图编辑模式
        if(gameProgress == 2){
            g.setFont(new Font(null,Font.ROMAN_BASELINE,20));
            g.setColor(Color.YELLOW);
            g.drawString("1、鼠标左击可添加方块",30,100);
            g.drawString("2、鼠标右击可取消方块",30,140);
            g.drawString("3、鼠标左键或右键按住拖拽可快速操作",30,180);

            //循环遍历方块区域 -1显示半透明方块底部，其他显示方块底部
            for (int i = 0; i < map.length; i++) {
                for (int j = map[i].length - 1; j >= 0; j--) {
                    Image tempImage = blockGrayImage;
                    if(map[i][j] != -1){
                        tempImage = blockImage;
                    }
                    //绘制方块
                    g.drawImage(tempImage, area_x + j * 31,
                            area_y + i * 35, 34, 39,this);
                }
            }

            return;
        }

        //下面是正常游戏 gameProgress = 1 时执行的

        //循环遍历map，非-1则生成button
        //这里是倒序读取，因为方块之间有相互遮盖的层次，后绘制可以遮盖前绘制，正序读取很难看
        for (int i = 0; i < map.length; i++) {
            for (int j = map[i].length - 1; j >= 0; j--) {
                if(map[i][j] != -1){
                    //绘制方块
                    g.drawImage(blockImage, area_x + j * 31,
                            area_y + i * 35, 34, 39,this);
                    //从list取图片，显示到方块上
                    int temp = blockMap.get(i + "," + j);
                    //更新map中的值为图片的数字
                    map[i][j] = (byte)temp;
                    //绘制图片
                    g.drawImage(images[0][temp], area_x + j * 31 + 3,
                            area_y + i * 35, 31, 35,this);
                }
            }
        }

        //如果有图片被选中，绘制被选中样式
        if(select_x != -1){
            g.drawImage(selectImage, area_x + select_x * 31 + 3,
                    area_y + select_y * 35, 31, 35,this);
        }
        //播放闪电动画
        //不过经过我的仔细观察，QQ连连看消除的闪电不只是素材的一根，
        //所以绘制了两根交错的闪电，达到高仿效果
        if(currentLightningIndex != -1){
            //如果是水平方向绘制
            if(lightning_type == LightningTypes.X){
                //获取方块中心点的鼠标x坐标 取中心点，所以最后-15
                int begin_x = area_x + (point_begin_fst.x + 1) * 31 - 15;
                int end_x = area_x + (point_end_fst.x + 1) * 31 - 15;
                //获取方块中心点的鼠标y坐标 取中心点，所以最后-17
                int y = area_y + (point_begin_fst.y + 1) * 35 - 17;
                //大约一个方块(X31,Y35)的距离绘制一次闪电
                int min = Math.min(begin_x,end_x);
                int count = Math.abs(begin_x - end_x) / 31;
                for (int i = 0; i < count; i++) {
                    int temp = currentLightningIndex%4;
                    g.drawImage(lightningXImages[temp],min + i * 31,y - 17,
                            35,30,this);
                    temp = (currentLightningIndex + 2)%4;
                    g.drawImage(lightningXImages[temp],min + i * 31,y - 17,
                            35,30,this);
                }
            }
            //如果是垂直方向绘制，从素材中提取出来的垂直闪电很丑，所以直接将横的闪电改成垂直拿来用
            if(lightning_type == LightningTypes.Y){
                //获取方块中心点的鼠标x坐标 取中心点，所以最后-15
                int x = area_x + (point_begin_fst.x + 1) * 31 - 15;
                //获取方块中心点的鼠标y坐标 取中心点，所以最后-17
                int begin_y = area_y + (point_begin_fst.y + 1) * 35 - 17;
                int end_y = area_y + (point_end_fst.y + 1) * 35 - 17;
                //大约一个方块(X31,Y35)的距离绘制一次闪电
                int min = Math.min(begin_y,end_y);
                int count = Math.abs(begin_y - end_y) / 35;
                for (int i = 0; i < count; i++) {
                    int temp = currentLightningIndex%4;
                    g.drawImage(lightningYImages[temp],x - 15,min + i * 35,
                            35,30,this);
                    temp = (currentLightningIndex + 2)%4;
                    g.drawImage(lightningYImages[temp],x - 15,min + i * 35,
                            35,30,this);
                }
            }

            //一折线绘制 先横后竖
            if(lightning_type == LightningTypes.XY){
                //横线
                //获取方块中心点的鼠标x坐标 取中心点，所以最后-15
                int begin_x = area_x + (point_begin_fst.x + 1) * 31 - 15;
                int end_x = area_x + (point_end_fst.x + 1) * 31 - 15;
                //获取方块中心点的鼠标y坐标 取中心点，所以最后-17
                int y = area_y + (point_begin_fst.y + 1) * 35 - 17;
                //大约一个方块(X31,Y35)的距离绘制一次闪电
                int min = Math.min(begin_x,end_x);
                int count = Math.abs(begin_x - end_x) / 31;
                for (int i = 0; i < count; i++) {
                    int temp = currentLightningIndex%4;
                    g.drawImage(lightningXImages[temp],min + i * 31,y - 17,
                            35,30,this);
                    temp = (currentLightningIndex + 2)%4;
                    g.drawImage(lightningXImages[temp],min + i * 31,y - 17,
                            35,30,this);
                }

                //竖线
                //获取方块中心点的鼠标x坐标 取中心点，所以最后-15
                int x = area_x + (point_begin_sec.x + 1) * 31 - 15;
                //获取方块中心点的鼠标y坐标 取中心点，所以最后-17
                int begin_y = area_y + (point_begin_sec.y + 1) * 35 - 17;
                int end_y = area_y + (point_end_sec.y + 1) * 35 - 17;
                //大约一个方块(X31,Y35)的距离绘制一次闪电
                int min1 = Math.min(begin_y,end_y);
                int count1 = Math.abs(begin_y - end_y) / 35;
                for (int i = 0; i < count1; i++) {
                    int temp = currentLightningIndex%4;
                    g.drawImage(lightningYImages[temp],x - 15,min1 + i * 35,
                            35,30,this);
                    temp = (currentLightningIndex + 2)%4;
                    g.drawImage(lightningYImages[temp],x - 15,min1 + i * 35,
                            35,30,this);
                }
            }

            //一折线绘制 先竖后横
            if(lightning_type == LightningTypes.YX){

                //竖线
                //获取方块中心点的鼠标x坐标 取中心点，所以最后-15
                int x = area_x + (point_begin_fst.x + 1) * 31 - 15;
                //获取方块中心点的鼠标y坐标 取中心点，所以最后-17
                int begin_y = area_y + (point_begin_fst.y + 1) * 35 - 17;
                int end_y = area_y + (point_end_fst.y + 1) * 35 - 17;
                //大约一个方块(X31,Y35)的距离绘制一次闪电
                int min1 = Math.min(begin_y,end_y);
                int count1 = Math.abs(begin_y - end_y) / 35;
                for (int i = 0; i < count1; i++) {
                    int temp = currentLightningIndex%4;
                    g.drawImage(lightningYImages[temp],x - 15,min1 + i * 35,
                            35,30,this);
                    temp = (currentLightningIndex + 2)%4;
                    g.drawImage(lightningYImages[temp],x - 15,min1 + i * 35,
                            35,30,this);
                }

                //横线
                //获取方块中心点的鼠标x坐标 取中心点，所以最后-15
                int begin_x = area_x + (point_begin_sec.x + 1) * 31 - 15;
                int end_x = area_x + (point_end_sec.x + 1) * 31 - 15;
                //获取方块中心点的鼠标y坐标 取中心点，所以最后-17
                int y = area_y + (point_begin_sec.y + 1) * 35 - 17;
                //大约一个方块(X31,Y35)的距离绘制一次闪电
                int min = Math.min(begin_x,end_x);
                int count = Math.abs(begin_x - end_x) / 31;
                for (int i = 0; i < count; i++) {
                    int temp = currentLightningIndex%4;
                    g.drawImage(lightningXImages[temp],min + i * 31,y - 17,
                            35,30,this);
                    temp = (currentLightningIndex + 2)%4;
                    g.drawImage(lightningXImages[temp],min + i * 31,y - 17,
                            35,30,this);
                }
            }

            //二折线绘制 横竖横
            if(lightning_type == LightningTypes.XYX){
                //横线
                //获取方块中心点的鼠标x坐标 取中心点，所以最后-15
                int begin_x = area_x + (point_begin_fst.x + 1) * 31 - 15;
                int end_x = area_x + (point_end_fst.x + 1) * 31 - 15;
                //获取方块中心点的鼠标y坐标 取中心点，所以最后-17
                int y = area_y + (point_begin_fst.y + 1) * 35 - 17;
                //大约一个方块(X31,Y35)的距离绘制一次闪电
                int min = Math.min(begin_x,end_x);
                int count = Math.abs(begin_x - end_x) / 31;
                for (int i = 0; i < count; i++) {
                    int temp = currentLightningIndex%4;
                    g.drawImage(lightningXImages[temp],min + i * 31,y - 17,
                            35,30,this);
                    temp = (currentLightningIndex + 2)%4;
                    g.drawImage(lightningXImages[temp],min + i * 31,y - 17,
                            35,30,this);
                }

                //竖线
                //获取方块中心点的鼠标x坐标 取中心点，所以最后-15
                int x = area_x + (point_begin_sec.x + 1) * 31 - 15;
                //获取方块中心点的鼠标y坐标 取中心点，所以最后-17
                int begin_y = area_y + (point_begin_sec.y + 1) * 35 - 17;
                int end_y = area_y + (point_end_sec.y + 1) * 35 - 17;
                //大约一个方块(X31,Y35)的距离绘制一次闪电
                int min1 = Math.min(begin_y,end_y);
                int count1 = Math.abs(begin_y - end_y) / 35;
                for (int i = 0; i < count1; i++) {
                    int temp = currentLightningIndex%4;
                    g.drawImage(lightningYImages[temp],x - 15,min1 + i * 35,
                            35,30,this);
                    temp = (currentLightningIndex + 2)%4;
                    g.drawImage(lightningYImages[temp],x - 15,min1 + i * 35,
                            35,30,this);
                }

                //横线
                //获取方块中心点的鼠标x坐标 取中心点，所以最后-15
                int begin_x1 = area_x + (point_begin_thr.x + 1) * 31 - 15;
                int end_x1 = area_x + (point_end_thr.x + 1) * 31 - 15;
                //获取方块中心点的鼠标y坐标 取中心点，所以最后-17
                int y1 = area_y + (point_begin_thr.y + 1) * 35 - 17;
                //大约一个方块(X31,Y35)的距离绘制一次闪电
                int min2 = Math.min(begin_x1,end_x1);
                int count2 = Math.abs(begin_x1 - end_x1) / 31;
                for (int i = 0; i < count2; i++) {
                    int temp = currentLightningIndex%4;
                    g.drawImage(lightningXImages[temp],min2 + i * 31,y1 - 17,
                            35,30,this);
                    temp = (currentLightningIndex + 2)%4;
                    g.drawImage(lightningXImages[temp],min2 + i * 31,y1 - 17,
                            35,30,this);
                }
            }

            //二折线绘制 竖横竖
            if(lightning_type == LightningTypes.YXY){
                //竖线
                //获取方块中心点的鼠标x坐标 取中心点，所以最后-15
                int x = area_x + (point_begin_fst.x + 1) * 31 - 15;
                //获取方块中心点的鼠标y坐标 取中心点，所以最后-17
                int begin_y = area_y + (point_begin_fst.y + 1) * 35 - 17;
                int end_y = area_y + (point_end_fst.y + 1) * 35 - 17;
                //大约一个方块(X31,Y35)的距离绘制一次闪电
                int min1 = Math.min(begin_y,end_y);
                int count1 = Math.abs(begin_y - end_y) / 35;
                for (int i = 0; i < count1; i++) {
                    int temp = currentLightningIndex%4;
                    g.drawImage(lightningYImages[temp],x - 15,min1 + i * 35,
                            35,30,this);
                    temp = (currentLightningIndex + 2)%4;
                    g.drawImage(lightningYImages[temp],x - 15,min1 + i * 35,
                            35,30,this);
                }

                //横线
                //获取方块中心点的鼠标x坐标 取中心点，所以最后-15
                int begin_x = area_x + (point_begin_sec.x + 1) * 31 - 15;
                int end_x = area_x + (point_end_sec.x + 1) * 31 - 15;
                //获取方块中心点的鼠标y坐标 取中心点，所以最后-17
                int y = area_y + (point_begin_sec.y + 1) * 35 - 17;
                //大约一个方块(X31,Y35)的距离绘制一次闪电
                int min = Math.min(begin_x,end_x);
                int count = Math.abs(begin_x - end_x) / 31;
                for (int i = 0; i < count; i++) {
                    int temp = currentLightningIndex%4;
                    g.drawImage(lightningXImages[temp],min + i * 31,y - 17,
                            35,30,this);
                    temp = (currentLightningIndex + 2)%4;
                    g.drawImage(lightningXImages[temp],min + i * 31,y - 17,
                            35,30,this);
                }

                //竖线
                //获取方块中心点的鼠标x坐标 取中心点，所以最后-15
                int x1 = area_x + (point_begin_thr.x + 1) * 31 - 15;
                //获取方块中心点的鼠标y坐标 取中心点，所以最后-17
                int begin_y1 = area_y + (point_begin_thr.y + 1) * 35 - 17;
                int end_y1 = area_y + (point_end_thr.y + 1) * 35 - 17;
                //大约一个方块(X31,Y35)的距离绘制一次闪电
                int min2 = Math.min(begin_y1,end_y1);
                int count2 = Math.abs(begin_y1 - end_y1) / 35;
                for (int i = 0; i < count2; i++) {
                    int temp = currentLightningIndex%4;
                    g.drawImage(lightningYImages[temp],x1 - 15,min2 + i * 35,
                            35,30,this);
                    temp = (currentLightningIndex + 2)%4;
                    g.drawImage(lightningYImages[temp],x1 - 15,min2 + i * 35,
                            35,30,this);
                }
            }

        }
        //播放爆炸动画 注释参考上面的闪电动画逻辑
        if(currentExplodeIndex != -1){
            //根据不同折线获取爆炸的X，Y坐标
            int begin_x = 0;
            int end_x = 0;
            int begin_y = 0;
            int end_y = 0;
            if(lightning_type == LightningTypes.X){
                begin_x = area_x + (point_begin_fst.x + 1) * 31 - 15;
                end_x = area_x + (point_end_fst.x + 1) * 31 - 15;
                begin_y = area_y + (point_begin_fst.y + 1) * 35 - 17;
                end_y = begin_y;
            }
            if(lightning_type == LightningTypes.Y){
                begin_x = area_x + (point_begin_fst.x + 1) * 31 - 15;
                end_x = begin_x;
                begin_y = area_y + (point_begin_fst.y + 1) * 35 - 17;
                end_y = area_y + (point_end_fst.y + 1) * 35 - 17;
            }
            if(lightning_type == LightningTypes.XY || lightning_type == LightningTypes.YX){
                begin_x = area_x + (point_begin_fst.x + 1) * 31 - 15;
                begin_y = area_y + (point_begin_fst.y + 1) * 35 - 17;
                end_x = area_x + (point_end_sec.x + 1) * 31 - 15;
                end_y = area_y + (point_end_sec.y + 1) * 35 - 17;
            }
            if(lightning_type == LightningTypes.XYX || lightning_type == LightningTypes.YXY){
                begin_x = area_x + (point_begin_fst.x + 1) * 31 - 15;
                begin_y = area_y + (point_begin_fst.y + 1) * 35 - 17;
                end_x = area_x + (point_end_thr.x + 1) * 31 - 15;
                end_y = area_y + (point_end_thr.y + 1) * 35 - 17;
            }
            //爆炸动画本身60*61，要在中心点播放，就要X,Y都-30
            begin_x = begin_x - 30;
            end_x = end_x - 30;
            begin_y = begin_y - 30;
            end_y = end_y - 30;
            //播放爆炸
            g.drawImage(explodeImages[currentExplodeIndex],begin_x,begin_y,
                    60,61,this);
            g.drawImage(explodeImages[currentExplodeIndex],end_x,end_y,
                    60,61,this);
        }
    }

    /**
     * 双缓冲防止repaint画面闪烁
     * @param g
     */
    public void update(Graphics g) {
        //如果变量为空，在内存中开辟一块绘图空间
        if(offScreenImage == null) {
            offScreenImage = this.createImage(810, 638);
        }
        //获得这块绘图空间的画笔/绘图环境
        Graphics gOffScreen = offScreenImage.getGraphics();
        //绘制图案
        paint(gOffScreen);
        //一次性将绘制完的图形显示出来
        g.drawImage(offScreenImage, 0, 0, null);
    }

    /**
     * 返回[min,max]的随机整数
     */
    private int randomBlock(int min,int max){
        return new Random().nextInt(max-min+1) + min;
    }

    /**
     * 裁剪图片
     */
    private void imageCut(){
        // 获取源图像
        Image image = loadImage("image/iconList.png");
        // 分割图像
        images = ImageUtil.cutImage(image, 1, 52, 0, 0, 31, 35, 31, 0, this);
    }

    /**
     * 读取map文件
     */
    private byte[][] readMap(String fileName,String path,int count){

        byte[][] map = new byte[11][19];

        File file = new File(path + "/" + fileName);
        //存入地图数据
        try(
            BufferedReader in = new BufferedReader(new FileReader(file));
        ) {
            String line;
            int row = 0;
            while((line = in.readLine()) != null){
                //因为数据不多，一次就能读完
                String[][] arr = toStringArray(line);
                for(int j=0;j<arr.length;j++){
                    for(int i=0;i<arr[j].length;i++){
                        if("-1".equals(arr[j][i])){
                            map[j][i] = -1;
                            continue;
                        }
                        map[j][i] = Byte.valueOf(arr[j][i]);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return map;
    }

    /**
     * 因为不想用fastjson包，而且map格式固定的，因此自己写了个转换
     */
    private String[][] toStringArray(String str){

        String[][] returnArr = new String[11][19];

        str = str.replaceAll(" ","");//去空格
        str = str.substring(2,str.length() - 2);//去头尾
        String[] splits = str.split("],\\[");

        for (int i = 0; i < splits.length; i++) {
            String[] temps = splits[i].split(",");
            returnArr[i] = temps;
        }

        return returnArr;
    }

    /**
     * 方块消除判断
     */
    private boolean blockClear(Point begin,Point end){
        Graphics g = this.getGraphics();
        //水平方向判断
        if(horizon(begin,end)){
            //方块消除处理
            blockBoom(begin,end,g);
            return true;
        };
        //垂直方向判断
        if(vertical(begin,end)){
            blockBoom(begin,end,g);
            return true;
        };
        //一折点判断
        if(brokenLineOnce(begin,end)){
            blockBoom(begin,end,g);
            return true;
        }
        //两折点判断
        if(brokenLineTwice(begin,end)){
            blockBoom(begin,end,g);
            return true;
        }
        return false;
    }

    /**
     * 方块消除处理
     * @param begin
     * @param end
     * @param g
     */
    private void blockBoom(Point begin,Point end,Graphics g){

        //选中方块置空
        select_x = -1;
        select_y = -1;
        //清除map中两个方块的值
        map[begin.y][begin.x] = -1;
        map[end.y][end.x] = -1;
        //清除blockMap对应值
        blockMap.put(begin.y + "," + begin.x,-1);
        blockMap.put(end.y + "," + end.x,-1);

        //播放闪电及爆炸音效 手速太快音效没播放完会导致新的播放不了，所以要先stop
        sound.sound_lightning.stop();
        sound.sound_lightning1.stop();
        sound.sound_explode.stop();
        sound.sound_lightning.play();
        sound.sound_lightning1.play();
        sound.sound_explode.play();
        //播放连线闪电及爆炸动画，在一个timer中调用即可，多个会影响刷新频率
        //如果手速快，第一次播放未结束就会调用第二次，因此每次调用前要初始化变量
        currentLightningIndex = -1;
        currentExplodeIndex = -1;
        if(animationTimmer == null){
            animationTimmer = new Timer(30, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    if(currentExplodeIndex >= 36){
                        currentExplodeIndex = -2;//就算停止了定时任务，还是会全部执行完，所以这里-2，下面++就变成-1了
                        animationTimmer.stop();//停止定时任务
                    }
                    if(currentExplodeIndex <= 18 && currentExplodeIndex%3 == 0){
                        currentLightningIndex++;
                    }else if(currentExplodeIndex > 18){
                        currentLightningIndex = -1;
                    }
                    currentExplodeIndex++;
                    update(g);
                }
            });
        }
        animationTimmer.start();
        //刷新方块数
        int count = countBlock();
        //方块数为0则胜利
        if(count == 0){
            //播放音效
            sound.sound_win.play();
            JOptionPane.showMessageDialog(this, "恭喜你获得了胜利！");
        }
    }

    /**
     * 判断方块是否在同一水平方向可消除
     */
    private boolean horizon(Point begin,Point end){

        if(begin.y != end.y){
            return false;
        }

        //两点之间是否有障碍物
        int min = Math.min(begin.x, end.x);
        int max = Math.max(begin.x, end.x);
        for (int i = min + 1; i < max; i++) {
            if(map[begin.y][i] != -1){
                return false;
            }
        }

        //如果可以连通，则设定类型和绘制坐标
        lightning_type = LightningTypes.X;
        point_begin_fst = begin;
        point_end_fst = end;

        return true;
    }

    /**
     * 判断方块是否在同一垂直方向可消除
     */
    private boolean vertical(Point begin,Point end){

        if(begin.x != end.x){
            return false;
        }

        //两点之间是否有障碍物
        int min = Math.min(begin.y, end.y);
        int max = Math.max(begin.y, end.y);
        for (int i = min + 1; i < max; i++) {
            if(map[i][begin.x] != -1){
                return false;
            }
        }

        //如果可以连通，则设定类型和绘制坐标
        lightning_type = LightningTypes.Y;
        point_begin_fst = begin;
        point_end_fst = end;

        return true;
    }

    /**
     * 判断方块是否一次折线即可消除
     */
    private boolean brokenLineOnce(Point begin,Point end){

        //如果两个方块通过一次折线即可连通，那么他们就可以形成一个矩形
        //找到另外两个矩形点
        Point begin_side = new Point(end.x,begin.y);
        Point end_side = new Point(begin.x,end.y);

        //有两种消除路线，begin->begin_side->end  begin->end_side->end
        //分别判断是否能连通

        //路线一：begin->begin_side->end
        if(map[begin_side.y][begin_side.x] == -1){//如果矩形点本身就有障碍，也不行
            if(horizon(begin,begin_side)){
                if(vertical(begin_side,end)){
                    //如果可以连通，则设定类型和绘制坐标
                    lightning_type = LightningTypes.XY;
                    point_begin_fst = begin;
                    point_end_fst = begin_side;
                    point_begin_sec = begin_side;
                    point_end_sec = end;
                    return true;
                }
            }
        }

        //路线二：begin->end_side->end
        if(map[end_side.y][end_side.x] == -1){//如果矩形点本身就有障碍，也不行
            if(vertical(begin,end_side)){
                if(horizon(end_side,end)){
                    //如果可以连通，则设定类型和绘制坐标
                    lightning_type = LightningTypes.YX;
                    point_begin_fst = begin;
                    point_end_fst = end_side;
                    point_begin_sec = end_side;
                    point_end_sec = end;
                    return true;
                }
            }
        }

        return false;
    }


    /**
     * 判断方块是否两次折线即可消除
     */
    private boolean brokenLineTwice(Point begin,Point end){

        //如果两个方块通过两次折线即可连通，那么可以拆分为 1条水平/垂直线 + 1条一折线
        //因此可以遍历 begin 上下左右四条线上的所有点，判断这些点能否和 end 进行一次折线连接

        //循环遍历 begin 左边
        if(begin.x > 0){ //为0左边就没有方块了
            for (int i = 0; i < begin.x; i++) {
                Point tempPoint = new Point(begin.x - (i + 1),begin.y);
                //左边点本身若为障碍，直接停止循环
                if(map[tempPoint.y][tempPoint.x] != -1){
                    break;
                }
                //左边的点能否连通，有障碍就停止循环
                if(horizon(begin,tempPoint)){
                    //左边的点能否和 end 形成一折线
                    if(brokenLineOnce(tempPoint,end)){
                        //能够连通，设定类型和绘制坐标
                        setTypeAndLocaltion(LightningTypes.XYX,begin,tempPoint,end);
                        return true;
                    }
                }else{
                    break;
                }
            }
        }
        //循环遍历 begin 右边
        if(begin.x < 18){ //默认方块一行最多19个，为18则右边没方块了
            for (int i = begin.x; i < 18; i++) {
                Point tempPoint = new Point(i + 1,begin.y);
                //左边点本身若为障碍，直接停止循环
                if(map[tempPoint.y][tempPoint.x] != -1){
                    break;
                }
                //右边的点能否连通，有障碍就停止循环
                if(horizon(begin,tempPoint)){
                    //右边的点能否和 end 形成一折线
                    if(brokenLineOnce(tempPoint,end)){
                        //能够连通，设定类型和绘制坐标
                        setTypeAndLocaltion(LightningTypes.XYX,begin,tempPoint,end);
                        return true;
                    }
                }else{
                    break;
                }
            }
        }
        //循环遍历 begin 上边
        if(begin.y > 0){ //为0上边就没有方块了
            for (int i = 0; i < begin.y; i++) {
                Point tempPoint = new Point(begin.x,begin.y - (i + 1));
                //左边点本身若为障碍，直接停止循环
                if(map[tempPoint.y][tempPoint.x] != -1){
                    break;
                }
                //上边的点能否连通，有障碍就停止循环
                if(vertical(begin,tempPoint)){
                    //上边的点能否和 end 形成一折线
                    if(brokenLineOnce(tempPoint,end)){
                        //能够连通，设定类型和绘制坐标
                        setTypeAndLocaltion(LightningTypes.YXY,begin,tempPoint,end);
                        return true;
                    }
                }else{
                    break;
                }
            }
        }
        //循环遍历 begin 下边
        if(begin.y < 10){ //默认方块一列最多11个，为10则下边没方块了
            for (int i = begin.y; i < 10; i++) {
                Point tempPoint = new Point(begin.x,i + 1);
                //左边点本身若为障碍，直接停止循环
                if(map[tempPoint.y][tempPoint.x] != -1){
                    break;
                }
                //下边的点能否连通，有障碍就停止循环
                if(vertical(begin,tempPoint)){
                    //下边的点能否和 end 形成一折线
                    if(brokenLineOnce(tempPoint,end)){
                        //能够连通，设定类型和绘制坐标
                        setTypeAndLocaltion(LightningTypes.YXY,begin,tempPoint,end);
                        return true;
                    }
                }else{
                    break;
                }
            }
        }

        return false;
    }

    /**
     * 二折线设定类型和绘制坐标
     */
    public void setTypeAndLocaltion(LightningTypes type,Point begin,Point tempPoint,Point end){
        //如果可以连通，先获取一折线起点
        Point oncePoint = point_end_fst;
        //再设定类型和绘制坐标
        lightning_type = type;
        point_begin_fst = begin;
        point_end_fst = tempPoint;
        point_begin_sec = tempPoint;
        point_end_sec = oncePoint;
        point_begin_thr = oncePoint;
        point_end_thr = end;
    }

    @Override
    public void mouseClicked(MouseEvent e) {

    }

    @Override
    public void mousePressed(MouseEvent e) {

    }

    /**
     * 鼠标弹起来时触发
     * @param e
     */
    @Override
    public void mouseReleased(MouseEvent e) {
        //使用鼠标弹起触发不使用单击触发的原因：
        // mouseClicked-鼠标单击触发 方法有问题，必须要鼠标在同一个像素点上按下并弹起才能触发
        // 如果在这个过程中鼠标位置哪怕产生了一丁点颤动，都无法触发该方法
        // 因此经常会导致鼠标快速点击出问题

        //初始化未完成不触发
        if(gameProgress == -1){
            return;
        }

        //非左键单击直接拦截 如果是编辑模式的鼠标右击，且是点击的方块区域，不拦截
        if(e.getButton() != MouseEvent.BUTTON1){
            if(e.getButton() == MouseEvent.BUTTON3 && gameProgress == 2
                    && e.getSource() != saveButton && e.getSource() != cancelButton){
                int x = e.getX();
                int y = e.getY();
                if(!(x >= area_x && x < area_end_x && y >= area_begin_y && y < area_end_y)){
                    return;
                }
            }else{
                return;
            }
        }

        //如果是开始按钮
        if(e.getSource() == startButton){
            //播放开始音效
            sound.sound_start.play();
            //如果游戏进度为0，则修改进度为1，且播放背景音乐
            if(gameProgress == 0){
                setGameProgress(1);
                sound.sound_bg_default.loop();
            }
            //清除方块map
            blockMap.clear();
            //清除选中
            select_x = -1;
            select_y = -1;
            //获取选择的地图并随机方块
            randomBlock(false);
            //刷新
            update(this.getGraphics());
        }
        //如果是编辑按钮
        if(e.getSource() == editButton){
            //进入地图编辑模式
            setGameProgress(2);
            //重新播放音频
            sound.sound_bg_default.stop();
            sound.sound_bg_default.loop();
            //获取当前选中地图
            copyMap();
            //刷新
            update(this.getGraphics());
        }
        //如果是保存按钮
        if(e.getSource() == saveButton){
            //不能少于30个方块
            int count = countBlock();
            if(count < 30){
                JOptionPane.showMessageDialog(this, "至少30个方块才能保存为地图！");
                return;
            }
            //如果方块是单数，提示无法保存
            if(count%2 == 1){
                JOptionPane.showMessageDialog(this, "连连看的方块必须是偶数！");
                return;
            }
            //调用保存文件窗口
            boolean saveMap = saveMap();
            //保存了才刷新
            if(saveMap){
                //加载所有地图文件，并刷新下拉框数值
                //此处也可以将totalMap copy到更大的数组，再将新地图放到尾部
                //但是判断玩家是新增的地图还是修改了原来地图太麻烦了，有时间的可以自己修改尝试下
                loadMap();
                //提示保存成功
                JOptionPane.showMessageDialog(this, "保存成功，地图列表已刷新！");
            }
        }
        //如果是取消按钮
        if(e.getSource() == cancelButton){
            //回到 准备状态
            setGameProgress(0);
            //取消音乐播放
            sound.sound_bg_default.stop();
            //刷新
            update(this.getGraphics());
        }
        //如果是重列按钮
        if(e.getSource() == rearrangementButton){
            if(countBlock == 0){
                JOptionPane.showMessageDialog(this, "没有方块可以重列！");
                return;
            }
            //播放道具使用音效
            sound.sound_item.stop();
            sound.sound_item.play();
            //获取选择的地图并随机方块
            randomBlock(true);
            //刷新
            update(this.getGraphics());
        }

        //不是按钮，则判断是否为连连看区域
        //准备状态非按钮不触发
        if(gameProgress == 0){
            return;
        }

        int x = e.getX();
        int y = e.getY();
        Graphics g = this.getGraphics();
        //判断鼠标点击是否在连连看区域，获取在map中的值
        if(x >= area_x && x < area_end_x && y >= area_begin_y && y < area_end_y){
            int temp_x = (x - area_x) / 31;
            int temp_y = (y - area_begin_y) / 35;
            //如果是地图编辑模式
            if(gameProgress == 2){
                //左键 添加方块
                if(e.getButton() == MouseEvent.BUTTON1){
                    //如果本来是空，则变成方块
                    if(map[temp_y][temp_x] == -1){
                        map[temp_y][temp_x] = 0;
                        countBlock++;
                    }
                }else if(e.getButton() == MouseEvent.BUTTON3){
                    //右键 删除方块
                    //如果本来是方块，则变成空
                    if(map[temp_y][temp_x]  >= 0){
                        map[temp_y][temp_x] = -1;
                        countBlock--;
                    }
                }
                //刷新
                update(g);
                return;
            }

            //点击无方块区域，无效
            if(map[temp_y][temp_x] == -1){
                return;
            }
            //播放点击音效
            sound.sound_click.stop();
            sound.sound_click.play();
            //点击本身，无效
            if(temp_x == select_x && temp_y == select_y){
                return;
            }
            //判断是否已有图片被选中
            if(select_x == -1){
                //无法消除则修改被选中图片坐标
                selectBlock(temp_x,temp_y,g);
            }else{
                //如果选中图片和已被选中图片一致，判断是否可消除
                if(map[temp_y][temp_x] == map[select_y][select_x]){
                    boolean clear = blockClear(new Point(select_x, select_y),new Point(temp_x, temp_y));
                    //如果无法消除则修改被选中图片坐标
                    if(!clear){
                        selectBlock(temp_x,temp_y,g);
                    }
                }else{
                    //无法消除则修改被选中图片坐标
                    selectBlock(temp_x,temp_y,g);
                }
            }
        }
    }

    /**
     * 打开map保存面板
     */
    private boolean saveMap(){
        String comboboxSelectName = mapComboBox.getSelectedItem().toString().substring(3,mapComboBox.getSelectedItem().toString().length());
        //弹出文件保存框
        JFileChooser jfc = new javax.swing.JFileChooser();
        //设置默认打开目录
        File file = new File(mapPath + "/" + comboboxSelectName + ".map");
        jfc.setCurrentDirectory(file);
        //只允许选择文件
        jfc.setFileSelectionMode(JFileChooser.FILES_ONLY);
        //只允许保存.map文件
        FileNameExtensionFilter filter = new FileNameExtensionFilter("map File", "map");
        jfc.addChoosableFileFilter(filter);
        //将map文件设置为默认保存文件
        jfc.setFileFilter(filter);
        //设置默认保存文件名称
        jfc.setSelectedFile(file);
        //将map中大于0的值全部替换成0
        for (int i = 0; i < map.length; i++) {
            for (int j = 0; j < map[0].length; j++) {
                if(map[i][j] > 0){
                    map[i][j] = 0;
                }
            }
        }
        //保存文件
        if(JFileChooser.APPROVE_OPTION == jfc.showSaveDialog(this)) {
            File saveFile = jfc.getSelectedFile();
            //如果玩家没有填写.map后缀名，则帮他加上
            String filePath = saveFile.getPath();
            if(!filePath.endsWith(".map")){
                saveFile = new File(filePath + ".map");
            }
            try(
                FileWriter fileWriter = new FileWriter(saveFile);
            ) {
                if(!saveFile.exists()){
                    saveFile.createNewFile();
                }
                fileWriter.write(Arrays.deepToString(map));
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            return true;
        }
        return false;
    }

    /**
     * 获取当前选中地图
     */
    private void copyMap(){
        //取当前选中地图，循环遍历，生成方块 -1为空，其他数字对应分割图片的数组下标
        byte[][] tempMap = new byte[11][19];
        for (int i = 0; i < tempMap.length; i++) {
            for (int j = 0; j < tempMap[0].length; j++) {
                tempMap[i][j] = totalMap[mapComboBox.getSelectedIndex()][i][j];
            }
        }
        map = tempMap;
        //刷新剩余方块数量
        countBlock();
    }

    /**
     * 获取方块总数
     * @return
     */
    private int countBlock(){
        int count = 0;
        for (int i = 0; i < map.length; i++) {
            for (int j = 0; j < map[0].length; j++) {
                if(map[i][j] >= 0){
                    count++;
                }
            }
        }
        //全局方块总数，用于paint刷新
        countBlock = count;
        return count;
    }

    /**
     * 获取当前选中地图，并随机方块
     * @param rearrangement 是否重列，如果是重列则不重置地图
     */
    private void randomBlock(boolean rearrangement){

        LinkedList<Integer> list = new LinkedList<>();
        LinkedList<String> saveKeyList = new LinkedList<>();

        //如果是重列则不重置地图
        if(rearrangement == false){
            //获取当前选中地图
            copyMap();
            //第一遍先获取方块总数
            int count = countBlock();
            //然后根据方块总数生成 随机方块/2 的集合
            for (int m = 0; m < count/2; m++) {
                int ran = randomBlock(0,51);
                //连连看，所以每个要两个方块
                list.add(ran);
                list.add(ran);
            }
            //保存map中方块坐标，后面会作为key，存入blockMap
            for (int i = 0; i < map.length; i++) {
                for (int j = 0; j < map[0].length; j++) {
                    if(map[i][j] != -1){
                        saveKeyList.add(i + "," + j);
                    }
                }
            }

        }else{
            //重列的话则循环获取剩下的blockMap中的方块 并重置blockMap
            for(String key : blockMap.keySet()){
                int value = blockMap.get(key);
                if(value != -1){
                    list.add(value);
                    saveKeyList.add(key);
                    blockMap.put(key,-1);
                }
            }
        }
        //将方块打乱保存
        ArrayList<Integer> arrayList1 = new ArrayList<>();
        for (int i = 0; i < list.size(); i++) {
            arrayList1.add(list.get(i));
        }
        for (int i = 0; i < list.size(); i++) {
            int index = arrayList1.size() == 1 ? 0 : randomBlock(0,arrayList1.size() - 1);
            int temp = arrayList1.get(index);
            //用map来保存随机方块，这样才能在消除方块后不影响其他方块
            blockMap.put(saveKeyList.getFirst(),temp);
            saveKeyList.removeFirst();
            arrayList1.remove(index);
        }
    }

    /**
     * 无法消除则修改被选中图片坐标
     * @param x
     * @param y
     * @param g
     */
    private void selectBlock(int x,int y,Graphics g){
        //修改坐标为被选中图片坐标
        select_x = x;
        select_y = y;
        //刷新绘图，被选中图片会增加选择框样式
        update(g);
    }

    @Override
    public void mouseEntered(MouseEvent e) {

    }

    @Override
    public void mouseExited(MouseEvent e) {

    }

    /**
     * 鼠标拖动监控
     * @param e
     */
    @Override
    public void mouseDragged(MouseEvent e) {
        //非编辑模式不处理
        if(gameProgress != 2){
            return;
        }
        if(!(e.getModifiers() == MouseEvent.BUTTON1_MASK || e.getModifiers() == MouseEvent.BUTTON3_MASK)){
            return;
        }
        if(e.getSource() == saveButton && e.getSource() == cancelButton){
            return;
        }
        int x = e.getX();
        int y = e.getY();
        if(x >= area_x && x < area_end_x && y >= area_begin_y && y < area_end_y){
            int temp_x = (x - area_x) / 31;
            int temp_y = (y - area_begin_y) / 35;
            //左键 添加方块
            if(e.getModifiers() == MouseEvent.BUTTON1_MASK){
                //如果本来是空，则变成方块
                if(map[temp_y][temp_x] == -1){
                    map[temp_y][temp_x] = 0;
                    countBlock++;
                    //刷新
                    update(this.getGraphics());
                }
            }else if(e.getModifiers() == MouseEvent.BUTTON3_MASK){
                //右键 删除方块
                //如果本来是方块，则变成空
                if(map[temp_y][temp_x]  >= 0){
                    map[temp_y][temp_x] = -1;
                    countBlock--;
                    //刷新
                    update(this.getGraphics());
                }
            }
        }
    }

    @Override
    public void mouseMoved(MouseEvent e) {

    }

    /**
     * 下拉框动作监听，地图编辑模式切换地图自动刷新
     * @param e
     */
    @Override
    public void actionPerformed(ActionEvent e) {
        if(gameProgress != 2){
            return;
        }
        //下拉框无值时不触发
        if(mapComboBox.getSelectedIndex() == -1){
            return;
        }
        //如果下拉框变动，重新加载编辑地图
        //获取当前选中地图
        copyMap();
        //刷新
        update(this.getGraphics());
    }

    /**
     * 修改游戏进度
     * @param gameProgress
     */
    private void setGameProgress(int gameProgress){
        this.gameProgress = gameProgress;

        //不同游戏进度的按钮显隐
        if(gameProgress == 0){
            startButton.setVisible(true);
            editButton.setVisible(true);
            saveButton.setVisible(false);
            cancelButton.setVisible(false);
        }
        if(gameProgress == 1){
            rearrangementButton.setVisible(true);
        }else{
            rearrangementButton.setVisible(false);
        }
        if(gameProgress == 2){
            saveButton.setVisible(true);
            cancelButton.setVisible(true);
            startButton.setVisible(false);
            editButton.setVisible(false);
        }
    }

    /**
     * 导出jar时调整map目录方法
     */
    public String exportJarMapDir(String path){
        path = path.substring(4, path.lastIndexOf("/"));
        path = path.substring(0, path.lastIndexOf("/")) + "/map";
        return path;
    }

    public static void main(String[] args) throws MalformedURLException {

        GameFrame gameFrame = new GameFrame();

        //初始化声音对象
        gameFrame.sound = new Sound(gameFrame);
        //音效资源加载完后更改游戏进度
        gameFrame.setGameProgress(0);
        //从-1到0时的按钮显隐
        gameFrame.comboboxLabel.setVisible(true);
        gameFrame.mapComboBox.setVisible(true);
        gameFrame.startButton.setVisible(true);
        gameFrame.editButton.setVisible(true);
        //刷新
        gameFrame.update(gameFrame.getGraphics());
    }

}
